﻿
using System.ComponentModel;
using System;
using System.IO;
using System.Text;
using Monitoring;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Collections.Generic;

namespace Monitoring {

    
    internal static class FSMon {

        /// <summary>
        /// Defines the base for all custom messages in this module.
        /// </summary>
        /// <remarks></remarks>
        internal const uint WM_MYBASE = WindowApi.WM_USER + 0x100;
    
        /// <summary>
        /// Signals that a change has occurred within the context of a watched folder.
        /// </summary>
        /// <remarks></remarks>
        internal const uint WM_SIGNAL = WM_MYBASE + 1;
    
        /// <summary>
        /// Signals the message pump to attempt to clear the cache of processed events.
        /// </summary>
        /// <remarks></remarks>
        internal const uint WM_SIGNAL_CLEAN = WM_MYBASE + 2;
    
        /// <summary>
        /// Signals that the monitor has been opened.
        /// </summary>
        /// <remarks></remarks>
        internal const uint WM_SIGNAL_OPEN = WM_MYBASE + 3;
    
        /// <summary>
        /// Signals that the monitor has been closed.
        /// </summary>
        /// <remarks></remarks>
        internal const uint WM_SIGNAL_CLOSE = WM_MYBASE + 4;
    
    
        /// <summary>
        /// The beginning of custom error codes.
        /// </summary>
        /// <remarks></remarks>
        internal const uint ERROR_MIN = 0x20000;
    
        /// <summary>
        /// Specifies that a mirroring action has failed.
        /// </summary>
        /// <remarks></remarks>
        internal const uint ERROR_MIRRORFAIL = ERROR_MIN + 1;
    
        /// <summary>
        /// Specifies that a failure regarding a specific path has occurred.
        /// </summary>
        /// <remarks></remarks>
        internal const uint ERROR_PATHFAIL = ERROR_MIN + 2;
    
        /// <summary>
        /// Specifies that a failure regarding a specific file has occurred.
        /// </summary>
        /// <remarks></remarks>
        internal const uint ERROR_FILEFAIL = ERROR_MIN + 3;
    
        /// <summary>
        /// Specifies that a failure regarding a specific destination path has occurred.
        /// </summary>
        /// <remarks></remarks>
        internal const uint ERROR_DESTPATHFAIL = ERROR_MIN + 4;
    
        /// <summary>
        /// Specifies that a failure regarding a specific destination file has occurred.
        /// </summary>
        /// <remarks></remarks>
        internal const uint ERROR_DESTFILEFAIL = ERROR_MIN + 5;
    
        [DllImport("kernel32.dll", CharSet=CharSet.Unicode, EntryPoint="CreateEventW")]
        internal static extern IntPtr CreateEvent(IntPtr lpEventAttributes, bool bManualReset, bool bInitialState, string lpName);
        
        [DllImport("kernel32.dll")]
        internal static extern bool SetEvent(IntPtr hEvent);
        
        [DllImport("kernel32.dll")]
        internal static extern bool ResetEvent(IntPtr hEvent);
        
        
        [DllImport("kernel32.dll", CharSet=CharSet.Unicode, EntryPoint="FindFirstChangeNotificationW")]
        internal static extern IntPtr FindFirstChangeNotification(string lpPathName, bool bWatchSubtree, NotifyFilter dwNotifyFilter);
    
        [DllImport("kernel32.dll", CharSet=CharSet.Unicode, EntryPoint="ReadDirectoryChangesW")]
        internal static extern bool ReadDirectoryChanges(IntPtr hDirectory,
                                                         IntPtr lpBuffer,
                                                         int nBufferLength,
                                                         bool bWatchSubtree,
                                                         NotifyFilter dwNotifyFilter,
                                                         ref int lpBytesreturned,
                                                         IntPtr lpOverlapped,
                                                         IntPtr lpCompletionRoutine);
                                                         
    }
    
    /// <summary>
    /// Sets the filter criteria for directory change notifications.
    /// </summary>
    /// <remarks></remarks>
    [Flags]
    public enum NotifyFilter
    {
        /// <summary>
        ///  Any file name change in the watched directory or subtree causes a change notification wait operation to return. Changes include renaming, creating, or deleting a file.
        /// </summary>
        [Description("Any file name change in the watched directory or subtree causes a change notification wait operation to return. Changes include renaming, creating, or deleting a file.")]
        NotifyFileRename = 0x1,
    
        /// <summary>
        ///  Any directory-name change in the watched directory or subtree causes a change notification wait operation to return. Changes include creating or deleting a directory.
        /// </summary>
        [Description("Any directory-name change in the watched directory or subtree causes a change notification wait operation to return. Changes include creating or deleting a directory.")]
        NotifyDirectoryRename = 0x2,
    
        /// <summary>
        ///  Any attribute change in the watched directory or subtree causes a change notification wait operation to return.
        /// </summary>
        [Description("Any attribute change in the watched directory or subtree causes a change notification wait operation to return.")]
        NotifyAttributesChange = 0x4,
    
        /// <summary>
        ///  Any file-size change in the watched directory or subtree causes a change notification wait operation to return. The operating system detects a change in file size only when the file is written to the disk. For operating systems that use extensive caching, detection occurs only when the cache is sufficiently flushed.
        /// </summary>
        [Description("Any file-size change in the watched directory or subtree causes a change notification wait operation to return. The operating system detects a change in file size only when the file is written to the disk. For operating systems that use extensive caching, detection occurs only when the cache is sufficiently flushed.")]
        NotifySizeChange = 0x8,
    
        /// <summary>
        ///  Any change to the last write-time of files in the watched directory or subtree causes a change notification wait operation to return. The operating system detects a change to the last write-time only when the file is written to the disk. For operating systems that use extensive caching, detection occurs only when the cache is sufficiently flushed.
        /// </summary>
        [Description("Any change to the last write-time of files in the watched directory or subtree causes a change notification wait operation to return. The operating system detects a change to the last write-time only when the file is written to the disk. For operating systems that use extensive caching, detection occurs only when the cache is sufficiently flushed.")]
        NotifyWrite = 0x10,
    
        /// <summary>
        ///  Any change to the last access time of files in the watched directory or subtree causes a change notification wait operation to return.
        /// </summary>
        [Description("Any change to the last access time of files in the watched directory or subtree causes a change notification wait operation to return.")]
        NotifyAccess = 0x20,
    
        /// <summary>
        ///  Any change to the creation time of files in the watched directory or subtree causes a change notification wait operation to return.
        /// </summary>
        [Description("Any change to the creation time of files in the watched directory or subtree causes a change notification wait operation to return.")]
        NotifyCreate = 0x40,
    
        /// <summary>
        ///  Any security-descriptor change in the watched directory or subtree causes a change notification wait operation to return.
        /// </summary>
        [Description("Any security-descriptor change in the watched directory or subtree causes a change notification wait operation to return.")]
        NotifySecurityChange = 0x100
    
    }
    
    /// <summary>
    /// Specifies an action on a file or a folder.
    /// </summary>
    /// <remarks></remarks>
    public enum FileActions
    {
        /// <summary>
        ///  The file was added to the directory.
        /// </summary>
        [Description(" The file was added to the directory.")]
        Added = 0x1,
    
        /// <summary>
        ///  The file was removed from the directory.
        /// </summary>
        [Description(" The file was removed from the directory.")]
        Removed = 0x2,
    
        /// <summary>
        ///  The file was modified. This can be a change in the time stamp or attributes.
        /// </summary>
        [Description(" The file was modified. This can be a change in the time stamp or attributes.")]
        Modified = 0x3,
    
        /// <summary>
        ///  The file was renamed and this is the old name.
        /// </summary>
        [Description(" The file was renamed and this is the old name.")]
        RenamedOldName = 0x4,
    
        /// <summary>
        ///  The file was renamed and this is the new name.
        /// </summary>
        [Description(" The file was renamed and this is the new name.")]
        RenamedNewName = 0x5
    
    }
    
    /// <summary>
    /// Class that provides information about an event that has occurred on a watch file system item.
    /// </summary>
    /// <remarks></remarks>
    public class FileNotifyInfo
    {
        private string fileName;
        private FileActions action;
        private FileNotifyInfo next;
    
        /// <summary>
        /// returns the old filename of a renamed file.
        /// </summary>
        /// <value></value>
        /// <returns></returns>
        /// <remarks></remarks>
        public string OldName
        {
            get
            {
                return fileName;
            }
        }
    
        /// <summary>
        /// returns the new filename of a renamed file.
        /// </summary>
        /// <value></value>
        /// <returns></returns>
        /// <remarks></remarks>
        public string NewName {
            get
            {
                if (next != null)
                    return next.Filename;
                else
                    return null;
            }
        }
    
        /// <summary>
        /// returns the filename upon which the action occurred.
        /// </summary>
        /// <value></value>
        /// <returns></returns>
        /// <remarks></remarks>
        public string Filename
        {
            get
            {
                return fileName;
            }
        }
    
        /// <summary>
        /// Specifies the action that occurred.
        /// </summary>
        /// <value></value>
        /// <returns></returns>
        /// <remarks></remarks>
        public FileActions Actions
        {
            get
            {
                return action;
            }
        }
    
        /// <summary>
        /// Gets the next entry in a chain of actions.
        /// </summary>
        /// <value></value>
        /// <returns></returns>
        /// <remarks></remarks>
        public FileNotifyInfo NextEntry
        {
            get
            {
                return next;
            }
        }
    
        /// <summary>
        /// Initialize a new instance of this class with the specified FILE_NOTIFY_INFORMATION structure.
        /// </summary>
        /// <param name="fni"></param>
        /// <remarks></remarks>
        internal FileNotifyInfo(FILE_NOTIFY_INFORMATION fni)
        {
            fileName = fni.Filename;
            action = fni.Action;
            if (fni.NextEntryOffset > 0)
            {
                next = new FileNotifyInfo(fni.NextEntry);
            }
        }
    
    }
    
    /// <summary>
    /// File notification structure (pointer structure).
    /// </summary>
    /// <remarks></remarks>
    [StructLayout(LayoutKind.Sequential)]
    public struct FILE_NOTIFY_INFORMATION
    {
        public MemPtr ptr;
    
        /// <summary>
        /// Gets the new name of a renamed file.
        /// </summary>
        /// <value></value>
        /// <returns></returns>
        /// <remarks></remarks>
        public string NewName
        {
            get
            {
                return NextEntry.Filename;
            }
        }
    
        /// <summary>
        /// Gets the old name of a renamed file.
        /// </summary>
        /// <value></value>
        /// <returns></returns>
        /// <remarks></remarks>
        public string OldName
        {
            get
            {
                return Filename;
            }
        }
    
        /// <summary>
        /// Gets the byte offset of the next entry relative to the pointer for this item.
        /// </summary>
        /// <value></value>
        /// <returns></returns>
        /// <remarks></remarks>
        public int NextEntryOffset
        {
            get
            {
                if (ptr == IntPtr.Zero) return 0;
                return ptr.intAt(0);
            }
            set
            {
                if (ptr == IntPtr.Zero) return;
                ptr.intAt(0, value);
            }
        }
    
        /// <summary>
        /// Specifies the action that occurred.
        /// </summary>
        /// <value></value>
        /// <returns></returns>
        /// <remarks></remarks>
        public FileActions Action
        {
            get
            {
                if (ptr == IntPtr.Zero) return 0;
                return (FileActions)(ptr.intAt(4));
            }
        }
    
        /// <summary>
        /// returns the length of the filename.
        /// </summary>
        /// <value></value>
        /// <returns></returns>
        /// <remarks></remarks>
        public int FilenameLength
        {
            get
            {
                if (ptr == IntPtr.Zero) return 0;
                return ptr.intAt(8);
            }
        }
    
        /// <summary>
        /// returns the filename.
        /// </summary>
        /// <value></value>
        /// <returns></returns>
        /// <remarks></remarks>
        public string Filename
        {
            get
            {
                if ((ptr == IntPtr.Zero) || (FilenameLength == null)) return null;
                return System.Text.Encoding.Unicode.GetString(ptr.GrabBytes(12, FilenameLength));
            }
        }
    
        /// <summary>
        /// returns the pointer to the next entry as a new FILE_NOTIFY_INFORMATION pointer structure.
        /// </summary>
        /// <value></value>
        /// <returns></returns>
        /// <remarks></remarks>
        public FILE_NOTIFY_INFORMATION NextEntry
        {
            get
            {
                if (NextEntryOffset <= 0) return new FILE_NOTIFY_INFORMATION();
    
                FILE_NOTIFY_INFORMATION m = new FILE_NOTIFY_INFORMATION();
    
                m.ptr = ptr;
                m.ptr += NextEntryOffset;
    
                return m;
            }
        }
    
    }
    
    /// <summary>
    /// Class that specifies details and arguments for an FSMonitor event.
    /// </summary>
    /// <remarks></remarks>
    public class FSMonitorEventArgs : EventArgs, IDisposable
    {
        internal bool handled = false;
    
        private string storage;
        private FileNotifyInfo info;
        private FSMonitor sender;
    
        /// <summary>
        /// Create a new event arguments from the specified information.
        /// </summary>
        /// <param name="inf"></param>
        /// <param name="wnd"></param>
        /// <returns></returns>
        /// <remarks></remarks>
        internal static FSMonitorEventArgs FromPtr(FILE_NOTIFY_INFORMATION inf, FSMonitor wnd)
        {
            var fsm = new FSMonitorEventArgs();
    
            fsm.info = new FileNotifyInfo(inf);
            fsm.sender = wnd;
            fsm.storage = wnd.Storage;
    
            return fsm;
        }
    
        /// <summary>
        /// Specifies the action for the event.
        /// </summary>
        /// <value></value>
        /// <returns></returns>
        /// <remarks></remarks>
        public FileActions Action
        {
            get
            {
                return info.Actions;
            }
        }
    
        /// <summary>
        /// Gets the FileNotifyInfo object for the event.
        /// </summary>
        /// <value></value>
        /// <returns></returns>
        /// <remarks></remarks>
        public FileNotifyInfo Info
        {
            get
            {
                return info;
            }
        }
    
        /// <summary>
        /// returns the associated IStorage implementation for the event.
        /// </summary>
        /// <value></value>
        /// <returns></returns>
        /// <remarks></remarks>
        public string Storage
        {
            get
            {
                return storage;
            }
            internal set
            {
                storage = value;
            }
        }
    
        /// <summary>
        /// Specifies the owning FSMonitor object.
        /// </summary>
        /// <value></value>
        /// <returns></returns>
        /// <remarks></remarks>
        public FSMonitor Sender
        {
            get
            {
                return sender;
            }
            internal set
            {
                sender = value;
            }
        }
    
        internal FSMonitorEventArgs()
        {
           
        }
    
        internal FSMonitorEventArgs(FSMonitor sender, string stor, FILE_NOTIFY_INFORMATION n, FileActions a)
        {
            this.sender = sender;
            storage = stor;
        }
    
    #region IDisposable Support

        private bool disposedValue; // To detect redundant calls
    
        // IDisposable
        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposedValue)
            {
                info = null;
            }
            this.disposedValue = true;
        }
    
        ~FSMonitorEventArgs()
        {
            // Do not change this code.  Put cleanup code in Dispose(ByVal bool disposing) above.
            Dispose(false);
        }
    
        public void Dispose()
        {
            // Do not change this code.  Put cleanup code in Dispose(bool disposing) above.
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    #endregion
    
    }
    
    /// <summary>
    /// Reasons for monitor closure.
    /// </summary>
    /// <remarks></remarks>
    public enum MonitorClosedState
    {
        /// <summary>
        /// The monitor was closed by a user action or a normal program event.
        /// </summary>
        /// <remarks></remarks>
        Closed,
    
        /// <summary>
        /// The monitor was closed because of some error.
        /// </summary>
        /// <remarks></remarks>
        ClosedOnError,
    
        /// <summary>
        /// The monitor was closed because the directory it was watching was deleted.
        /// </summary>
        /// <remarks></remarks>
        ClosedOnRemove
    }
    
    /// <summary>
    /// Class to describe a MonitorClosed event.
    /// </summary>
    /// <remarks></remarks>
    public class MonitorClosedEventArgs : EventArgs
    {
     
        private MonitorClosedState _cs;
    
        private int _ec = 0;
    
        private string _em;
    
        /// <summary>
        /// Gets the error message, if any.
        /// </summary>
        /// <value></value>
        /// <returns></returns>
        /// <remarks></remarks>
        public string ErrorMessage
        {
            get
            {
                return _em;
            }
        }
    
        /// <summary>
        /// Gets the error code, if any.
        /// </summary>
        /// <value></value>
        /// <returns></returns>
        /// <remarks></remarks>
        public int ErrorCode 
        {
            get
            {
                return _ec;
            }
        }
    
        /// <summary>
        /// Gets the closed state of the object.
        /// </summary>
        /// <value></value>
        /// <returns></returns>
        /// <remarks></remarks>
        public MonitorClosedState ClosedState
        {
            get
            {
                return _cs;
            }
        }
    
        /// <summary>
        /// Create a new instance of this class with the specified closed state.
        /// </summary>
        /// <param name="cs">The monitor closed state.</param>
        /// <remarks></remarks>
        internal MonitorClosedEventArgs(MonitorClosedState cs)
        {
            _cs = cs;
        }
    
        /// <summary>
        /// Create a new instance of this class with the specified closed state and error code.
        /// </summary>
        /// <param name="cs">The monitor closed state.</param>
        /// <param name="ec">The error code.</param>
        /// <remarks></remarks>
        internal MonitorClosedEventArgs(MonitorClosedState cs, int ec)
        {
            _cs = cs;
            _ec = ec;
        }
    
        /// <summary>
        /// Create a new instance of this class with the specified closed state, error code and error message.
        /// </summary>
        /// <param name="cs">The monitor closed state.</param>
        /// <param name="ec">The error code.</param>
        /// <param name="em">The error message.</param>
        /// <remarks></remarks>
        internal MonitorClosedEventArgs(MonitorClosedState cs, int ec, string em)
        {
            _cs = cs;
            _ec = ec;
            _em = em;
        }
    
    }
    
    /// <summary>
    /// Class to watch the file system.
    /// </summary>
    /// <remarks></remarks>
    public class FSMonitor : NativeWindow, IDisposable
    {
     
        [DllImport("user32.dll", CharSet=CharSet.Unicode, EntryPoint="PostMessageW")]
        internal static extern bool PostMessage(IntPtr hwnd, uint uMsg, IntPtr wParam, IntPtr lParam);
        
        [DllImport("kernel32.dll")]
        internal static extern uint GetLastError();
     
        [DllImport("kernel32", CharSet=CharSet.Unicode, EntryPoint="FormatMessageW")]
        internal static extern int FormatMessage(uint dwFlags, IntPtr lpSource, uint dwMessageId, uint dwLanguageId, IntPtr lpBuffer, uint dwSize, IntPtr va_list);

        /// <summary>
        /// Format a given system error, or the last system error by default.
        /// </summary>
        /// <param name="syserror">Format code to pass. GetLastError is used as by default.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        internal static string FormatLastError(uint syserror = 0) {

            uint err = (syserror == 0 ? GetLastError() : syserror);
        
            MemPtr mm = new MemPtr(1026);
            string s;

            FormatMessage(0x1000, IntPtr.Zero, err, 0, mm, 512, IntPtr.Zero);

            s = "Error 0x" + err.ToString("X8") + ": " + mm.ToString();
            mm.Free();

            return s;
        }


        protected string stor;
        protected IntPtr hFile;
        protected bool isWatching;
        protected System.Threading.Thread thread;
        protected MemPtr buff;
    
        // the default filter will be write, and rename (which includes delete, create and move).
        protected NotifyFilter filter = NotifyFilter.NotifyWrite | NotifyFilter.NotifyFileRename | NotifyFilter.NotifyDirectoryRename;
    
        // this is the action buffer that gets handled by the message pump.
        protected List<FSMonitorEventArgs> _WaitList = new List<FSMonitorEventArgs>();
    
        protected FSMonitorEventArgs sigAdd;
    
        protected int waitLock = 0;
        protected int lastIndex = 0;
    
        protected IntPtr owner;
    
        /// <summary>
        /// The event that get fired when a change is detected in the monitored path.
        /// </summary>
        /// <remarks></remarks>
        public event EventHandler<FSMonitorEventArgs> WatchNotifyChange;
        
        protected virtual void OnWatchNotifyChange(Object sender, FSMonitorEventArgs e)
        {
            EventHandler<FSMonitorEventArgs> handler = WatchNotifyChange;
            if (handler != null) handler(this, e);
        }
        
        
        /// <summary>
        /// The event that gets fired when the monitor is opened.
        /// </summary>
        /// <remarks></remarks>
        public event EventHandler MonitorOpened;
     
        protected virtual void OnMonitorOpened(Object sender, EventArgs e)
        {
            EventHandler handler = MonitorOpened;
            if (handler != null) handler(this, e);
        }
        
        /// <summary>
        /// The event that gets fired when the monitor is closed.
        /// </summary>
        /// <remarks></remarks>
        public event EventHandler<MonitorClosedEventArgs> MonitorClosed;
       
        protected virtual void OnMonitorClosed(Object sender, MonitorClosedEventArgs e)
        {
            EventHandler<MonitorClosedEventArgs> handler = MonitorClosed;
            if (handler != null) handler(this, e);
        }
        
        /// <summary>
        /// Gets or sets the NotifyFilter criteria for this monitor object.
        /// </summary>
        /// <value></value>
        /// <returns></returns>
        /// <remarks></remarks>
        public NotifyFilter Filter
        {
            get
            {
                return filter;
            }
            set
            {
                filter = value;
                if (isWatching)
                {
                    StopWatching();
                    Watch();
                }
            }
        }
    
        /// <summary>
        /// Gets a value indicating if the monitor thread is running.
        /// </summary>
        /// <value></value>
        /// <returns>True if the monitor is open.</returns>
        /// <remarks></remarks>
        public bool IsWatching
        {
            get
            {
                return isWatching;
            }
        }
    
        /// <summary>
        /// Retrieves the open file handle.
        /// </summary>
        /// <value></value>
        /// <returns></returns>
        /// <remarks></remarks>
        public IntPtr FileHandle
        {
            get
            {
                return hFile;
            }
        }
    
        /// <summary>
        /// Retrieves the associated IStorage object.
        /// </summary>
        /// <value></value>
        /// <returns></returns>
        /// <remarks></remarks>
        public string Storage
        {
            get
            {
                return stor;
            }
        }
    
        /// <summary>
        /// returns the owner window of this monitor, if any.
        /// </summary>
        /// <value></value>
        /// <returns></returns>
        /// <remarks></remarks>
        public IntPtr Owner
        {
            get
            {
                return owner;
            }
        }
    
    
        /// <summary>
        /// Create and activate the file system monitor thread.
        /// </summary>
        /// <returns>
        /// True if the thread was successfully created.  
        /// To ensure the thread was successfully activated, handle the MonitorOpened event.
        /// </returns>
        /// <remarks></remarks>
        public bool Watch()
        {
            if (isWatching) return false;
    
            return internalWatch();
        }
    
        /// <summary>
        /// Deactivate and destroy the file system monitor thread.
        /// </summary>
        /// <returns>True if the thread was successfully deactivated and the file handle was closed.</returns>
        /// <remarks></remarks>
        public bool StopWatching()
        {
            if (!isWatching) return false;
    
            if (thread != null)
            {
                thread.Abort();
            }
    
            internalCloseFile();
            return true;
        }
    
        /// <summary>
        /// Initialize a new instance of the FSMonitor class with the specified IStorage object.
        /// </summary>
        /// <param name="stor">The storage path that will be the target of the monitor.</param>
        /// <remarks></remarks>
        public FSMonitor(string stor)
        {
            this.stor = stor;
            internalCreate();
        }
    
        /// <summary>
        /// Initialize a new instance of the FSMonitor class with the specified IStorage object and parent window handle.
        /// </summary>
        /// <param name="stor">The storage path that will be the target of the monitor.</param>
        /// <param name="hwndOwner">The handle to the owner window.</param>
        /// <remarks></remarks>
        public FSMonitor(string stor, IntPtr hwndOwner)
        {
            this.stor = stor;
            owner = hwndOwner;
            internalCreate();
        }
    
        private bool internalCreate()
        {
            try
            {
                MonitorDispatch.Create(this, owner);
            }
            catch
            {
                FAIL_STRUCT f = new FAIL_STRUCT();
                disposedValue = true;
    
                f.Origin = "FSMonitor";
                f.FailCode = (int)FSMon.ERROR_MIRRORFAIL;
                f.OriginType = FailOrigins.Watcher;
                f.Buffer = stor;
    
                FailDispatch.AlertFail(ref f);
                return false;
            }
    
            // 64k is definitely enough when the thread is running continuously.
            // barring something funky blocking us ... 
            return buff.Alloc(65535, true);
    
        }
    
        /// <summary>
        /// Internally perform the actions necessary to open the target directory.
        /// </summary>
        /// <returns>True if a file handle was successfully acquired.</returns>
        /// <remarks></remarks>
        protected bool internalOpenFile()
        {
    
            hFile = FileIO.CreateFile(stor,
                                Monitoring.FileAccess.GenericRead | 
                                Monitoring.FileAccess.ReadAttributes | 
                                Monitoring.FileAccess.ReadData | 
                                Monitoring.FileAccess.ReadEA | 
                                Monitoring.FileAccess.ListDirectory | 
                                Monitoring.FileAccess.Traverse, 
                                ShareModes.Delete | 
                                ShareModes.Read | 
                                ShareModes.Write, 
                                IntPtr.Zero, 
                                CreateDisposition.OpenExisting, 
                                FileFlags.BackupSemantics | 
                                FileFlags.Overlapped, 
                                IntPtr.Zero);
    
            if (hFile == (IntPtr)(-1))
            {
                var f = new FAIL_STRUCT();
                disposedValue = true;
    
                f.Origin = "FSMonitor";
                f.FailCode = (int)FSMon.ERROR_MIRRORFAIL;
                f.OriginType = FailOrigins.Watcher;
                f.Buffer = stor;
    
                FailDispatch.AlertFail(ref f);
                return false;
            }
    
            return true;
        }
    
        /// <summary>
        /// Internally performs the actions necessary to close the file handle to the associated folder.
        /// </summary>
        /// <remarks></remarks>
        protected void internalCloseFile()
        {
            if (FileIO.CloseHandle(hFile))
            {
                hFile = IntPtr.Zero;
            }
        }
    
        /// <summary>
        /// Internally creates and starts the monitor thread.
        /// </summary>
        /// <returns>
        /// True if the thread was successfully created.  
        /// To ensure the monitor was successfully activated, handle the MonitorOpened event.
        /// </returns>
        /// <remarks></remarks>
        protected bool internalWatch()
        {
    
            FILE_NOTIFY_INFORMATION fn;
            int blen = 0;
            int bufflen = (int)(buff.GetLength());
            IntPtr tbuff = buff.Handle;
    
            if (thread != null) return false;
    
            if (!internalOpenFile()) return false;
    
            fn.ptr = buff;
    
            thread = new System.Threading.Thread( 
             () => {
                    IntPtr notice = IntPtr.Zero;
                    PostMessage(Handle, FSMon.WM_SIGNAL_OPEN, IntPtr.Zero, IntPtr.Zero);
    
                    while(1==1)
                    {
                        try {
                            blen = 0;
                            if (!FSMon.ReadDirectoryChanges(hFile,
                                    tbuff,
                                    bufflen,
                                    true,
                                    filter,
                                    ref blen,
                                    IntPtr.Zero,
                                    IntPtr.Zero))
                            {
    
                                notice = (IntPtr)(GetLastError());
                                break;
                            }
                        }
                        catch(System.Threading.ThreadAbortException)
                        {
                            PostMessage(Handle, FSMon.WM_SIGNAL_CLOSE, IntPtr.Zero, IntPtr.Zero);
                            thread = null;
                            return;
                        }
                        catch
                        {
                            notice = (IntPtr)1;
                            break;
                        }
    
                        // block until the lock is acquired.  Hopefully the
                        // UI thread will not take that long to clean the list.
                        System.Threading.Monitor.Enter(_WaitList);
    
                        _WaitList.Add(FSMonitorEventArgs.FromPtr(fn, this));
                        // and we//re done ...
                        System.Threading.Monitor.Exit(_WaitList);
    
                        // post to the UI thread that there are items to dequeue and continue!
                        PostMessage(Handle, FSMon.WM_SIGNAL, IntPtr.Zero, IntPtr.Zero);
    
                        // let//s clean up the memory before the next execute.
                        if (blen > 0) buff.ZeroMemory(0, blen);
                    }
    
                    internalCloseFile();
                    thread = null;
    
                    PostMessage(Handle, FSMon.WM_SIGNAL_CLOSE, IntPtr.Zero, notice);
                });
    
            thread.SetApartmentState(System.Threading.ApartmentState.STA);
            thread.IsBackground = true;
    
            if (thread.ThreadState != System.Threading.ThreadState.Running) thread.Start();
    
            return true;
    
        }
    
        /// <summary>
        /// Internal message pump handler and event dispatcher.
        /// </summary>
        /// <param name="m"></param>
        /// <remarks></remarks>
        protected override void WndProc(ref Message m)
        {
            int i, c;
            
            switch((uint) m.Msg)
            {
    
                case FSMon.WM_SIGNAL:
                    
                    // there are items waiting to be dequeued, let//s dequeue one. 
                    FSMonitorEventArgs e;
    
                    c = _WaitList.Count - 1;
                    i = lastIndex;
    
                    // make sure we're not jumping ahead of a previous cleaning.
                    if (i <= c)
                    {
                        e = _WaitList[i];
    
                        if (e != null)
                        {
                            // post the events so that whatever is watching this folder can do its thing.
    
                            OnWatchNotifyChange(this, e);
    
                            // remove the item from its slot in the queue, thereby
                            // eliminating any chance the same event will be fired, again.
                            _WaitList[i] = null;
                        }
    
                        lastIndex = i + 1;
    
                        // post a message to the queue cleaner.  if there are more files, it will send the pump back this way.
                        PostMessage(Handle, FSMon.WM_SIGNAL_CLEAN, IntPtr.Zero, IntPtr.Zero);
    
                    }
                    
                    break;
    
                case FSMon.WM_SIGNAL_CLEAN:
                
                    // don't block on the main thread, block on the watching thread, instead.
                    if (System.Threading.Monitor.TryEnter(_WaitList))
                    {
    
                        // we have a lock, let//s clean up the queue
                        c = _WaitList.Count - 1;
    
                        for (i = c; i >= 0; i--)
                        {
                            // we want to only remove slots that have been dequeued.
                            if (_WaitList[i] == null)
                            {
                                _WaitList.RemoveAt(i);
                            }
                        }
    
                        // reset the lastindex to 0, indicating that any items still in the queue have not fired, yet.
                        lastIndex = 0;
                        System.Threading.Monitor.Exit(_WaitList);
    
                        // if we still have more signals in the queue, tell the message pump to keep on truckin'.
                        if (_WaitList.Count > 0) PostMessage(Handle, FSMon.WM_SIGNAL, IntPtr.Zero, IntPtr.Zero);
                    }
                    else
                    {
                        // oh snap!  can't lock it, let's send another clean message to make sure we do finally execute, eventually. 
                        PostMessage(Handle, FSMon.WM_SIGNAL_CLEAN, IntPtr.Zero, IntPtr.Zero);
                    }
    
                    break;
                    
                case FSMon.WM_SIGNAL_OPEN:
    
                    isWatching = true;
                    OnMonitorOpened(this, new EventArgs());
                    
                    break;
    
                case FSMon.WM_SIGNAL_CLOSE:
    
                    isWatching = false;
    
                    if ((int)m.LParam >= 1)
                        OnMonitorClosed(this, new MonitorClosedEventArgs(MonitorClosedState.ClosedOnError, (int)m.LParam, FormatLastError((uint)m.LParam)));
                    else
                        OnMonitorClosed(this, new MonitorClosedEventArgs(MonitorClosedState.Closed));
                    
                    break;
    
                default:

                    base.WndProc(ref m);
                    break;
            }
    
        }
    
    #region IDisposable Support
    
        /// <summary>
        /// returns true if the monitor has been disposed.  
        /// if it has been disposed, it may not be reused.
        /// </summary>
        /// <value></value>
        /// <returns></returns>
        /// <remarks></remarks>
        public bool Disposed
        {
            get
            {
                return disposedValue;
            }
        }
    
        private bool disposedValue; // To detect redundant calls
        private bool shuttingDown;
    
        /// <summary>
        /// Dispose of the managed and unmanaged resources.
        /// </summary>
        /// <param name="disposing"></param>
        /// <remarks></remarks>
        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposedValue)
            {
                shuttingDown = true;
    
                // shut down the watching thread
                if (isWatching) StopWatching();
                lastIndex = 0;
    
                // destroy the waiting queue
                _WaitList = null;
                waitLock = 0;
    
                // Remove us from the dispatch list
                MonitorDispatch.Remove(this);
    
                // destroy the window handle
                this.DestroyHandle();
    
                // free the buffer
                buff.Free(true);
    
                // release the storage
                stor = null;
            }
    
            this.disposedValue = true;
            this.shuttingDown = false;
        }
    
        ~FSMonitor()
        {
            Dispose(false);
        }
    
        /// <summary>
        /// Deactivate the monitor, destroy the window handle and dispose of any managed or unmanaged resources.
        /// </summary>
        /// <remarks></remarks>
        public void Dispose()
        {
            // Do not change this code.  Put cleanup code in Dispose(bool disposing) above.
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    #endregion
    
    }

}